﻿/***
 * 
 * 功能：从AVI视频指定位置抽取指定长度的图像帧，并通过回调返回
 * 依赖：EmguCV 3.3.0.2824 ，通过 Nuget包管理器下载官方提供的库(必须要求3.3.0版本，其它版本可能存在一些bug)
 * 
 ***/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;


namespace AVIFile
{
    public class AVICapture
    {
        #region 回调

        /// <summary>
        /// 读取到帧图像回调函数：一旦外部调用Start后，会逐帧返回要截取时间段的Bitmap
        /// </summary>
        public Action<Bitmap> GetFrameImage = new Action<Bitmap>((Bitmap bmp) => { });

        #endregion

        #region 属性

        /// <summary>
        /// 原始视频最大持续时长，单位：秒
        /// </summary>
        public float MaxDuration
        {
            get
            {
                return (float)MaxFrameCount / (float)Fps;
            }
        }

        /// <summary>
        /// 原始视频的帧率
        /// </summary>
        public int Fps { get; private set; }

        /// <summary>
        /// 截取的起始时间
        /// </summary>
        private float startTime;
        public float StartTime
        {
            get
            {
                return startTime;
            }
            private set
            {
                if (0 <= value && value <= MaxDuration)
                    startTime = value;
            }
        }

        /// <summary>
        /// 要截取的时长，单位：秒
        /// </summary>
        private float duration;
        public float Duration
        {
            get
            {
                return duration;
            }
            private set
            {
                if (0 <= value && value <= MaxDuration)
                    duration = value;
            }
        }

        /// <summary>
        /// 原始视频的最大帧数
        /// </summary>
        public int MaxFrameCount { get; private set; }

        /// <summary>
        /// 要截取的帧索引，第一帧为0
        /// </summary>
        private int StartFrameIndex
        {
            get
            {
                return (int)(StartTime * Fps);
            }
        }

        /// <summary>
        /// 要截取的帧数，根据截取时长计算得出
        /// </summary>
        private int FrameCount
        {
            get
            {
                return (int)(Duration * Fps);
            }
        }

        #endregion

        #region 变量

        private VideoCapture capture;
        private Mat frame;
        private int width = 0;
        private int height = 0;
        #endregion

        #region 构造函数

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public AVICapture()
        {

        }

        /// <summary>
        /// 带参构造函数
        /// </summary>
        /// <param name="fileName"></param>
        public AVICapture(string fileName)
        {
            Open(fileName);
        }
        #endregion

        #region 方法

        private void Open(string fileName)
        {
            capture = new VideoCapture(fileName);
            this.Fps = (int)capture.GetCaptureProperty(CapProp.Fps);
            this.MaxFrameCount = (int)capture.GetCaptureProperty(CapProp.FrameCount);
            width = (int)capture.GetCaptureProperty(CapProp.FrameWidth);
            height = (int)capture.GetCaptureProperty(CapProp.FrameHeight);
        }

        /// <summary>
        /// 开始捕获图像
        /// </summary>
        /// <param name="start">截取起始时间 秒</param>
        /// <param name="duration">截取时长：秒</param>
        public void StartCapture(float start, float duration)
        {
            if (capture == null) return;
            this.StartTime = start;
            this.Duration = duration;
            frame = new Mat();
            capture.SetCaptureProperty(CapProp.PosFrames, this.StartFrameIndex);
            capture.ImageGrabbed += this.ProcessFrame;
            capture.Start();
        }

        /// <summary>
        /// EmguCV返回图像帧的方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProcessFrame(object sender, EventArgs e)
        {

            if (capture != null && capture.Ptr != IntPtr.Zero)
            {
                int index = (int)capture.GetCaptureProperty(CapProp.PosFrames);
                capture.Retrieve(frame, 0);
                if (StartFrameIndex + 1 <= index && index < StartFrameIndex + FrameCount + 1)
                {
                    if (frame != null)
                    {
                        GetFrameImage.Invoke(frame.Bitmap);
                    }
                }
                if (index == MaxFrameCount || index == StartFrameIndex + FrameCount)
                {
                    index = 0;
                    capture.SetCaptureProperty(CapProp.PosFrames, MaxFrameCount);
                    Stop();
                }
            }

        }

        public void Stop()
        {
            capture.ImageGrabbed -= this.ProcessFrame;
        }
        #endregion

    }
}
